#include "rpc_proxy.h"
#include "rpc_logger.h"
#include "rpc_root.h"

namespace rpc {

std::uint32_t ProxyCreator::proxyID_ = 1;

ProxyCreator::~ProxyCreator() {
    factoryMap_.clear();
}

ProxyManager::~ProxyManager() {
    proxyMap_.clear();
}

void ProxyManager::add(ProxyPtr proxy) {
    proxyMap_[proxy->getID()] = proxy;
}

void ProxyManager::destroy(ProxyID id) {
    proxyMap_.erase(id);
}

ProxyPtr ProxyManager::get(ProxyID id) {
    auto it = proxyMap_.find(id);
    if (it == proxyMap_.end()) {
        return nullptr;
    }
    return it->second;
}

ProxyCall::~ProxyCall() {
    if (timerHandle_) {
        ProxyCallManagerRef.getTimer().cancelTimer(timerHandle_);
    }
}

const std::string& ProxyCall::getCallName() {
#ifdef DEBUG
    return callName_;
#else
    static std::string unknown("unknown");
    return unknown;
#endif
}

CallID ProxyCall::getCallID() {
    return callID_;
}

ErrorID ProxyCall::getErrorID() {
    return errorID_;
}

bool ProxyCall::isCoroutinized() {
    return inCoro_;
}

void ProxyCall::setCoro() {
    inCoro_ = true;
}

void ProxyCall::setErrorID(ErrorID errorID) {
    errorID_ = errorID;
}

void ProxyCall::setCallID(CallID callID) {
    callID_ = callID;
}

void ProxyCall::setTimerHandle(TimerHandle handle) {
    timerHandle_ = handle;
}

void ProxyCall::setTimeout(int timeout) {
    timeout_ = timeout;
}

int ProxyCall::getTimeout() {
    return timeout_;
}

void ProxyCall::setRetry(int retry) {
    retry_ = retry;
}

int ProxyCall::getRetry() {
    return retry_;
}

bool ProxyCall::needRetry() {
    return (retry_ > 0);
}

TimerHandle ProxyCall::getTimerHandle() {
    return timerHandle_;
}

void ProxyCall::setNoExcept(bool noExcept) {
    noExcept_ = noExcept;
}

bool ProxyCall::getNoExcept() {
    return noExcept_;
}

void ProxyCall::resend() {
    if (transport_) {
        transport_->send(data_.get(), dataSize_);
    }
}

void ProxyCall::setData(const char* data, int size) {
    data_.reset(new char[size]);
    memcpy(data_.get(), data, size);
    dataSize_ = size;
}

void ProxyCall::startExecTimer() {
    if (timerHandle_) {
        ProxyCallManagerRef.getTimer().cancelTimer(timerHandle_);
        timerHandle_ = nullptr;
    }
    timerHandle_ = ProxyCallManagerRef.getTimer().addTimerOnce([&](std::uint64_t callID) {
        auto callPtr = ProxyCallManagerRef.get(static_cast<CallID>(callID));
        if (!callPtr) {
            return;
        }
        if (callPtr->needRetry()) {
            ProxyCallManagerRef.retry(callPtr);
        } else {
            ProxyCallManagerRef.timeout(callPtr);
        }
    }, getTimeout(), getCallID());
}

ServiceID Proxy::getTarget() {
    return targetID_;
}

void Proxy::setTarget(ServiceID serviceID) {
    targetID_ = serviceID;
}

TransportPtr Proxy::getTransport() {
    return transport_;
}

void Proxy::setTransport(TransportPtr transport) {
    transport_ = transport;
}

void Proxy::attach() {
    attached_ = true;
}

void Proxy::detach() {
    attached_ = false;
}

bool Proxy::isAttached() {
    return attached_;
}

void Proxy::setID(ProxyID id) {
    id_ = id;
}

ProxyID Proxy::getID() {
    return id_;
}

void ProxyCall::setCallName(const std::string& name) {
#ifdef DEBUG
    callName_ = name;
#else
    (void)name;
#endif
}

void ProxyCallManager::destroy(CallID callID) {
    auto it = callMap_.find(callID);
    if (it == callMap_.end()) {
        return;
    }
    callMap_.erase(it);
}

void ProxyCallManager::update() {
    while (timer_.runOnce()) {}
}

ProxyCallPtr ProxyCallManager::get(CallID callID) {
    auto it = callMap_.find(callID);
    if (it == callMap_.end()) {
        return nullptr;
    }
    return it->second;
}

void ProxyCallManager::retry(ProxyCallPtr callPtr) {
    Logbegin << "Retry method name [" << callPtr->getCallName() << "]" << Logend;
    callPtr->startExecTimer();
    callPtr->setRetry(callPtr->getRetry() - 1);
    callPtr->setErrorID(static_cast<ErrorID>(RpcError::TIMEOUT));
    callPtr->resend();
}

void ProxyCallManager::timeout(ProxyCallPtr callPtr) {
    Logbegin << "Proxy call timeout [" << callPtr->getCallName() << "]" << Logend;
    callPtr->setErrorID(static_cast<ErrorID>(RpcError::TIMEOUT));
    callPtr->doRet(0);
}

}